/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package genomemap.data;

import javautil.collections.ArrayUtil;
import javautil.collections.ListUtil;

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Vector;

/**
 * Genetic Data Source : this generalizes G_FileDataSource and G_DBDataSource
 * @author Susanta Tewari
 * @version 1.0
 * @created 08-Nov-2007 8:33:54 PM
 */
public abstract class GDataSource extends DataSource {

    /**
     * true: imputation will be applied
     */
    private RFLPImputation rflpImputation = new RFLPImputation();

    /**
     * RFLP Data
     */
    private int[][] rflpData = null;

    /**
     * for current ch_id and order
     * @return
     */
    public LinkedHashSet<Gene> getGeneSet() {

        LinkedHashSet<Gene> fullGeneSet = getFullGeneSet();
        LinkedHashSet<Gene> reqSet      = new LinkedHashSet<>();
        Iterator<Gene> itrGene          = fullGeneSet.iterator();

        while (itrGene.hasNext()) {

            Gene gene = itrGene.next();

            if (ArrayUtil.contains(getOrder(), null, gene.getOrder())) {
                reqSet.add(gene);
            }
        }

        return reqSet;
    }

    public LinkedHashSet<Gene> getSeqGeneSet() {

        LinkedHashSet<Gene> seqGeneSet  = new LinkedHashSet<>();
        LinkedHashSet<Gene> fullGeneSet = getFullGeneSet();

        for (Gene gene : fullGeneSet) {

            if (gene.hasSeqInfo()) {
                seqGeneSet.add(gene);
            }
        }

        return seqGeneSet;
    }

    public abstract LinkedHashSet<Gene> getFullGeneSet();

    public int[][] getArrayOfDistinctGenotypes(int[][] data) {

        Vector spores = new Vector();

        for (int sporeIndex = 0; sporeIndex < data[0].length; sporeIndex++) {

            Vector spore = new Vector();

            for (int geneIndex = 0; geneIndex < data.length; geneIndex++) {

                spore.add(new Integer(data[geneIndex][sporeIndex]));
            }

            spores.add(spore);
        }

        spores = new Vector(new LinkedHashSet(spores));


//      put the spores back in array
        int[][] genotype = new int[((Vector) spores.get(0)).size()][spores.size()];

        for (int projenyIndex = 0; projenyIndex < genotype[0].length; projenyIndex++) {

            Vector spore = (Vector) spores.get(projenyIndex);

            for (int geneIndex = 0; geneIndex < genotype.length; geneIndex++) {

                genotype[geneIndex][projenyIndex] = ((Integer) spore.get(geneIndex)).intValue();
            }
        }

        return genotype;
    }

    public int[] getArrayOfCellFreq(int[][] data) {

        Vector spores = new Vector();

        for (int sporeIndex = 0; sporeIndex < data[0].length; sporeIndex++) {

            Vector spore = new Vector();

            for (int geneIndex = 0; geneIndex < data.length; geneIndex++) {

                spore.add(new Integer(data[geneIndex][sporeIndex]));
            }

            spores.add(spore);
        }

        Vector distinctSpores = new Vector(new LinkedHashSet(spores));
        int[] cellFreq        = new int[distinctSpores.size()];

        for (int i = 0; i < cellFreq.length; i++) {

            cellFreq[i] = ListUtil.getFreq(spores, distinctSpores.get(i));
        }

        return cellFreq;
    }

    public double[] getInitialCProb(int[][] data) {

        double[] cProbInitial = new double[data.length - 1];

        for (int index = 0; index < data.length - 1; index++) {

            cProbInitial[index] = initialProb(data, index + 1);
        }

        return cProbInitial;
    }

    /**
     * returns  a new int[][] rflp data according to the current  value of the
     * folowing properties.
     * 1. gene order here)
     * 2. impute
     * @return
     */
    @Override
    public int[][] getdata() {
        return reOrderData(getRflpData(), getOrder());
    }

    /**
     * RFLP Data
     * @return
     */
    int[][] getRflpData() {

        if (rflpData == null) {
            loadData();
        }

        return rflpData;
    }

    public void setRflpData(int[][] newVal) {
        rflpData = newVal;
    }

    /**
     * retunrs the loci( # of markers)
     * @return
     */
    public int getLoci() {
        return getOrder().length;
    }

    /**
     * probe/clone order,gene order or sequence order
     *
     * @param newVal
     */
    @Override
    public void setOrder(int[] newVal) {

        if (newVal.length % 2 == 0) {
            super.setOrder(ArrayUtil.removeIndices(newVal, newVal.length - 1));
        } else {
            super.setOrder(newVal);
        }
    }

    /**
     * Method description
     *
     * @param array description
     * @param i description
     *
     * @return description
     */
    private static double initialProb(int[][] array, int i) {

        int obs   = array[0].length;
        int count = 0;
        double d, c;

        for (int index = 0; index < obs; index++) {

            if (((array[i - 1][index] == 1) && (array[i][index] == 0))
                    || ((array[i - 1][index] == 0) && (array[i][index] == 1))) {
                count++;
            }
        }

        d = (double) 2 * count / obs;

        if (d > 1.0) {
            c = 1.0 - (double) 2 / obs;
        } else {
            c = 1 - Math.sqrt(1 - d);
        }

        if ((c == 0) || (c > 1.0) || (c < 0)) {


            // avoid boundary 0 by a small number close to 0
            c = 0.0001;

            System.out.println(
                "From GDataSource: initialProb().  boundary or abnormal value of c: " + c);
            System.out.println("obs: " + obs);
            System.out.println("recom count: " + count);
        }

        return c;
    }

    /**
     * data returned is acording to the order length and its order.
     * Note that even if different lengths of orders are set on GDataSource there is no problem
     * since any computation gets data by invoking <CODE>getdata()</CODE> which returns
     * data according to the current order This becomes possible since for any order
     * when setting rflpData, after applying the imputation strategy the whole rflp data
     * is stored. This is done through method <CODE>filterData()</CODE> which doesn`t take
     * any order parameter.
     *
     * @param data description
     * @param order description
     *
     * @return description
     */
    private static int[][] reOrderData(int[][] data, int[] order) {

        int[][] newData = new int[order.length][data[0].length];

        for (int rowIndex = 0; rowIndex < order.length; rowIndex++) {

            for (int colIndex = 0; colIndex < data[0].length; colIndex++) {

                newData[rowIndex][colIndex] = data[order[rowIndex] - 1][colIndex];
            }
        }

        return newData;
    }

    /**
     * this is applied in the loadData() after the raw data is loaded
     * @param data
     * @return
     */
    public int[][] Imputation(int[][] data) {

        int policyIndex  = getRflpImputation().getPolicyIndex();
        int missingValue = getRflpImputation().getMissingValueCode();
        int geneNumber   = data.length;
        int sporeNumber  = data[0].length;

        if (policyIndex == RFLPImputation.POLICY_REMOVE) {


            // This will remove all the infected (missing value present)progeny
            boolean[] infected = new boolean[sporeNumber];
            int totalInfected  = 0;

            for (int index = 0; index < sporeNumber; index++) {

                if (ArrayUtil.contains(ArrayUtil.getCol(data, index + 1), null, missingValue)) {

                    totalInfected++;

                    infected[index] = true;
                }
            }

            int count2          = 0;
            int[][] trimd_data1 = new int[geneNumber][sporeNumber - totalInfected];

            for (int sporeIndex = 0; sporeIndex < sporeNumber; sporeIndex++) {

                if (!infected[sporeIndex]) {

                    for (int geneIndex = 0; geneIndex < geneNumber; geneIndex++) {

                        trimd_data1[geneIndex][count2] = data[geneIndex][sporeIndex];
                    }

                    count2++;
                }
            }

            return trimd_data1;
        }

        if (policyIndex == RFLPImputation.POLICY_PROB) {

            double imputationProb = getRflpImputation().getImputationProb();

            for (int geneIndex = 0; geneIndex < geneNumber; geneIndex++) {

                for (int sporeIndex = 0; sporeIndex < sporeNumber; sporeIndex++) {

                    if (data[geneIndex][sporeIndex] == missingValue) {

                        if (Math.random() < imputationProb) {
                            data[geneIndex][sporeIndex] = 0;
                        } else {
                            data[geneIndex][sporeIndex] = 1;
                        }
                    }
                }
            }

            return data;
        }

        return data;
    }

    /**
     * this gives the whole data set with imputation strategies applied.
     */
    protected static int[][] filterData(int[][] data, int missingValue) {

        int geneNumber  = data.length;
        int sporeNumber = data[0].length;
        int[] temp1     = new int[sporeNumber];
        int count1      = 0;

        for (int index = 0; index < sporeNumber; index++) {

            if (ArrayUtil.contains(ArrayUtil.getCol(data, index + 1), null, missingValue)) {

                count1++;

                temp1[index] = 0;    /* missing Value is present */

            } else {
                temp1[index] = 1;
            }
        }

        int count2          = 0;
        int[][] trimd_data1 = new int[geneNumber][sporeNumber - count1];

        for (int index1 = 0; index1 < sporeNumber; index1++) {

            if (temp1[index1] == 1) {

                for (int index2 = 0; index2 < geneNumber; index2++) {

                    trimd_data1[index2][count2] = data[index2][index1];
                }

                count2++;
            }
        }

        return trimd_data1;
    }

    RFLPImputation getRflpImputation() {
        return rflpImputation;
    }

    public void setRflpImputation(RFLPImputation rflpImputation) {
        this.rflpImputation = rflpImputation;
    }
}
